package com.newegg.research;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;

import com.newegg.research.datastructure.Heap;

public class Indexer
{
	private static Logger logger = Logger.getLogger(Indexer.class.getName());

	private Map<String, Category> all_categories = new HashMap<String, Category>();

	private Map<String, Token> all_tokens = new HashMap<String, Token>();

	private Map<String, String> brandMapping = new HashMap<String, String>();

	private Set<String> category_names = new HashSet<String>();

	private int words_count = 0;

	private static boolean isInitialzed = false;

	private static Indexer instance = new Indexer();

	private Indexer()
	{
	}

	public static Indexer getInstance()
	{
		return instance;
	}

	public Category getCategory(String name)
	{
		if (name == null)
			throw new IllegalArgumentException(
					"The requested category name cannot be null");

		if (all_categories.containsKey(name))
		{
			return all_categories.get(name);
		} else
		{
			String[] seg = Util.participle(name);
			category_names.addAll(Arrays.asList(seg));
			Category instance = new Category(name);
			all_categories.put(name, instance);
			return instance;
		}
	}

	public Token getToken(String word, boolean createNewOne)
	{
		if (word == null)
			throw new IllegalArgumentException(
					"The requested category name cannot be null");

		word = word.toLowerCase();

		boolean isBrand = false;
		boolean isProductID = false;

		if (brandMapping.containsKey(word))
		{
			// logger.info("商品品牌匹配:" + word + "==>" + brandMapping.get(word));
			// word = brandMapping.get(word);
			isBrand = true;
		}

		if (word.matches("[\\d|\\w]+"))
			isProductID = true;

		if (all_tokens.containsKey(word))
		{
			return all_tokens.get(word);
		} else if (createNewOne)
		{
			Token token = new Token(word, isBrand, isProductID);
			all_tokens.put(word, token);
			return token;
		} else
			return null;
	}

	public synchronized void init(String trainingFile, String brandMappingFile)
			throws IOException
	{
		if (isInitialzed)
		{
			logger.warning("The instance is initialized");
			return;
		}

		long start = System.currentTimeMillis();

		if (brandMappingFile != null)
		{
			File brand = new File(brandMappingFile);
			Iterator<String> it = FileUtils.lineIterator(brand);
			while (it != null && it.hasNext())
			{
				String line = it.next();
				String[] param = line.split(",");
				if (param.length == 2)
				{
					brandMapping.put(param[0].toLowerCase(), param[1]
							.toLowerCase());
					// if (param[1].length() > 1 &&
					// !param[1].matches("[\\w|\\d]+"))
					// brandMapping.put(param[0].toLowerCase(), param[1]
					// .toLowerCase().substring(0, 2));
					// else
					// brandMapping.put(param[0].toLowerCase(), param[1]
					// .toLowerCase());
				}
			}
			logger.info("索引商品中英文品牌映射共" + brandMapping.size() + "条");
		}

		File[] files = new File(trainingFile).listFiles();
		for (File f : files)
		{
			List<String> fileContent = FileUtils.readLines(f);
			if (fileContent.size() < 20)
				continue;
			else
			{
				String category = f.getName();
				for (String line : fileContent)
				{
					if (!line.contains(category))
					{
						line += category;
					}
					String[] tokens = Util.participle(line, brandMapping);
					for (String t : tokens)
					{
						if (t.length() > 1)
							insertWord(t, category);
					}
				}
			}
		}
		logger.info("The " + files.length + " files has been indexed");
		logger.info("共索引：" + getAllTokenNum() + "条数据");
		logger.info("总类别:" + getAllCategoriesAsMap().size() + "个");
		logger.info("包含词:" + getAll_tokens().size() + "个");
		logger.info("构造索引共耗时:" + (System.currentTimeMillis() - start) + " ms");
		isInitialzed = true;

	}

	public void insertWord(String word, String category)
	{
		Token t = getToken(word, true);
		Category c = getCategory(category);
		t.increaseCount(c);
		c.addToken(t);
		words_count++;
	}

	public int getAllTokenNum()
	{
		return words_count;
	}

	private double calcIDF(Token t)
	{
		double d;
		if (t.getSumCount() != 0)
		{
			d = Math.log(words_count / t.getSumCount()) / Math.log(2);
		} else
		{
			d = 0;
		}

		if (t.isProductID())
			d *= 1.4;
		else if (t.isBrand())
			d *= 1.1;
		else if (category_names.contains(t.getContent()))
			d *= 1.5;

		return d;
	}

	public double acos(List<Token> t, Category c)
	{
		double factor = 0;
		for (Token t1 : t)
		{
			if (t1.getCountInCategory(c) > 0)
			{
				factor += t1.getCountInCategory(c) * Math.pow(calcIDF(t1), 2);
			}
		}

		if (factor == 0)
		{
			factor = -1;
		}

		double result = Math
				.abs(1 - (factor / (getFactor1(c) * getFactor2(t))));
		return result;
	}

	private double getFactor1(Category c)
	{
		List<Token> tokens = c.getAllToken();
		double factor1 = 0;
		for (Token t : tokens)
		{
			double idf = calcIDF(t);
			factor1 += Math.pow(t.getCountInCategory(c) * idf, 2);
		}
		return Math.sqrt(factor1);
	}

	private double getFactor2(List<Token> t)
	{
		double factor = 0;
		for (Token t1 : t)
		{
			factor += Math.pow(calcIDF(t1), 2);
		}
		return Math.sqrt(factor);
	}

	public static void main(String[] args) throws IOException
	{
		logger.setLevel(Level.INFO);
		// Indexer index = new Indexer();
		Indexer index = Indexer.getInstance();
		// index.init("d:/Training_ALL/");
		Scanner scanner = new Scanner(System.in);

		String test = null;
		while (!(test = scanner.nextLine()).equals("!"))
		{
			long start = System.currentTimeMillis();
			System.out.println("Final Result:" + index.guessCategory(test));
			System.out.println("耗时: " + (System.currentTimeMillis() - start)
					+ " ms");
		}

		// List l = index.getAllCategory();
		// for(Object o : l)
		// {
		// System.out.println("当前类别："+ o);
		// }

		// List<Token> l = new ArrayList<Token>();
		// for (String s : participle(test))
		// {
		// Token t = index.getToken(s, false);
		// if (t != null)
		// l.add(t);
		// logger.info("分词结果:" + s);
		// }
		// List<Category> l1 = index.getAllCategory();
		// System.out.println(index.getAll_categories().containsKey("键盘"));
		// for (Token t : l)
		// {
		// int i1 = t.getSumCount();
		// System.out.println("词 " + t + " 共出现 " + i1 + " 次, 权重 "
		// + index.calcIDF(t));
		// for (Category c : l1)
		// {
		// if (t.getCountInCategory(c) > 0)
		// System.out.println(t + ":" + c + ":"
		// + t.getCountInCategory(c));
		// }
		// }

	}

	public List<Category> getAllCategory()
	{
		Set set = all_categories.entrySet();
		Iterator it = set.iterator();
		List l = new ArrayList(all_categories.size());
		while (it.hasNext())
		{
			l.add(((Entry) it.next()).getValue());
		}
		return l;
	}

	public Category guessCategory(String s)
	{
		String filtered_text = s.replaceAll("[(|（].*?[)|）]", "");
		String[] s1 = Util.participle(filtered_text, brandMapping);
		System.out.println(filtered_text);
		List<Token> l = new ArrayList<Token>();
		for (String str : s1)
		{
			Token t = this.getToken(str, false);
			if (t != null)
			{
				l.add(t);
			} else
			{
				logger.info("无效词:" + str);
			}
		}

		Set<Entry<String, Category>> set = all_categories.entrySet();
		Iterator<Entry<String, Category>> it = set.iterator();

		// List<Category> result = new ArrayList<Category>();
		Category max_ratio_cateogry = null;
		double max_ratio = Double.MAX_VALUE;

		Heap cosResult = new Heap();

		while (it != null && it.hasNext())
		{
			Entry<String, Category> entry = it.next();
			Category c = entry.getValue();
			double d = acos(l, c);

			if (d < 1)
			{
				cosResult.insert(c, 1 - (float) d);
				// result.add(c);
			}
			if (d < max_ratio)
			{
				max_ratio = d;
				max_ratio_cateogry = c;
			}
		}

		if (cosResult.size() == 0)
			return null;
		else if (cosResult.size() == 1)
			return (Category) cosResult.remove();
		else
		{
			// double max_words = 0;
			// Category best = null;
			Heap nameCompare = new Heap(cosResult.size());

			float firstRatio = 0;
			Category bestCategory = null;
			while (!cosResult.isEmpty())
			{
				Heap.Entry entry = cosResult.removeEntry();
				if (firstRatio == 0)
				{
					firstRatio = entry.getPriority();
					bestCategory = (Category) entry.getValue();
				} else if (firstRatio - entry.getPriority() > 0.1)
					break;

				Category c = (Category) entry.getValue();
				String name = c.getCategory_name();

				double count_ratio = 0;
				for (Token t : l)
				{
					if (name.contains(t.getContent()))
					{
						count_ratio += calcIDF(t);
					}
				}
				float f = (float)( count_ratio / l.size() );
				if( f > 2 )
					nameCompare.insert(c, (float) (count_ratio / l.size()));
				else
					break;
			}

			switch(nameCompare.size())
			{
			case 0: return bestCategory;
			case 1: return (Category) nameCompare.remove();
			default:
				Heap.Entry e1 = nameCompare.removeEntry();
				Heap.Entry e2 = nameCompare.removeEntry();
				if(e1.getPriority() - e2.getPriority() < 0.01 && e2.getValue().toString().length() < e1.getValue().toString().length())
					return (Category) e2.getValue();
				else
					return (Category) e1.getValue();
			}
		}
	}

	public Map<String, Category> getAllCategoriesAsMap()
	{
		return all_categories;
	}

	public void setAll_categories(Map<String, Category> all_categories)
	{
		this.all_categories = all_categories;
	}

	public Map<String, Token> getAll_tokens()
	{
		return all_tokens;
	}

	public void setAll_tokens(Map<String, Token> all_tokens)
	{
		this.all_tokens = all_tokens;
	}

	public static Logger getLogger()
	{
		return logger;
	}

	public static void setLogger(Logger logger)
	{
		Indexer.logger = logger;
	}

}
